from classfile.class_reader import MethodCodeReader
from classpath.class_path import BootStrapClassPath, ExtClassPath, ApplicationClassPath
from heap.base_class_loader import BootStrapClassLoader, ExtClassLoader, AppClassLoader
from insts.inst import Instruction
from insts.inst_helper import Instructions
from jthread.thread import JThread
from jvm.base_vm import BaseVM


class JVM(BaseVM):
    def __init__(self, cmd):
        super().__init__()
        self.mainThread = JThread.newThread()
        self.cmd = cmd
        self.classLoader = self.initClassLoader()

    def initClassLoader(self):
        bootstrapCp = BootStrapClassPath(jreOption=self.cmd.xjreOption)
        extCp = ExtClassPath(jreOption=self.cmd.xjreOption)
        appCp = ApplicationClassPath(cpOption=self.cmd.cpOption)

        bootstrapClassLoader = BootStrapClassLoader(classpath=bootstrapCp)
        extClassLoader = ExtClassLoader(classpath=extCp, parent=bootstrapClassLoader)
        appClassLoader = AppClassLoader(classpath=appCp, parent=extClassLoader)
        return appClassLoader

    def start(self):
        self.initVm()
        self.executeMain()

    def initVm(self):
        vmClass = self.classLoader.loadClass("sun/misc/VM")
        self.initClass(self.mainThread, vmClass)
        self.intercept(self.mainThread)

    def loadClass(self, className):
        return self.classLoader.loadClass(className=className)

    def executeMain(self):
        className = self.cmd.Class
        mainClass = self.classLoader.loadClass(className)
        mainMethod = mainClass.getMainMethod()
        if mainMethod is None:
            raise Exception("No main method found in class " + className)
        frame = self.mainThread.newFrame(mainMethod)
        frame.getLocalVars().setRef(0, self.createArgsArray(
            classLoader=self.classLoader, arr=["a", "b"], klass=self.classLoader.loadClass("java/lang/String")
        ))
        self.mainThread.pushFrame(frame)
        self.intercept(self.mainThread)

    def intercept(self, thread: JThread):
        reader = MethodCodeReader(data=[], pos=0)
        while True:
            # 获取线程中栈顶的frame
            topFrame = thread.topFrame()
            pc = topFrame.getNextPc()
            thread.setPc(pc)
            reader.reset(data=topFrame.getJMethod().getCode(), pos=pc)
            opcode = reader.readUInt8()
            inst: Instruction = Instructions.newInstruction(opcode)
            if inst is None:
                raise Exception(f"Unknown instruction: {opcode}")
            inst.fetchOperands(reader=reader)
            topFrame.setNextPc(reader.getPos())
            self.logInstruction(topFrame, inst)
            inst.execute(frame=topFrame)
            if thread.isEmpty():
                break

    def logInstruction(self, topFrame, inst):
        method = topFrame.getJMethod()
        className = method.getKlass().getName()
        methodName = method.getName()
        pc = topFrame.getJThread().getPc()
        print(f"{className}.{methodName} pc={pc} {inst}")
